import redisManager from './redisManager.js'
import { WS_COMMANDS, ERROR_TYPES, REDIS_PREFIXES } from '../constants/index.js'

/**
 * WebSocket 消息处理器
 */
class MessageHandler {
  constructor(connectionManager) {
    this.connectionManager = connectionManager
  }

  /**
   * 处理 WebSocket 消息
   */
  async handleMessage(message, conn, connId) {
    try {
      let messageObj = this.parseMessage(message)
      if (!messageObj) return

      // 兼容 { type } 写法
      if (messageObj.type && !messageObj.cmd) {
        messageObj.cmd = messageObj.type
      }

      console.log(`处理消息: ${messageObj.cmd}`)

      switch (messageObj.cmd) {
        case WS_COMMANDS.PING:
          this.handlePing(conn)
          break
        case WS_COMMANDS.LOGIN:
          await this.handleLogin(messageObj, conn, connId)
          break
        case WS_COMMANDS.START_CALL:
          await this.handleStartCall(messageObj, conn, connId)
          break
        case WS_COMMANDS.JOIN_ROOM:
          await this.handleJoinRoom(messageObj, conn, connId)
          break
        case WS_COMMANDS.OFFER:
        case WS_COMMANDS.ANSWER:
        case WS_COMMANDS.ICE:
          await this.handleRelay(messageObj)
          break
        case WS_COMMANDS.HANG_UP:
        case WS_COMMANDS.STOP_ROOM:
        case WS_COMMANDS.AV_STATUS:
          await this.handleRoomBroadcast(messageObj)
          break
        default:
          console.log('忽略未知命令:', messageObj.cmd)
          break
      }
    } catch (error) {
      console.error('消息处理失败:', error.message)
    }
  }

  /**
   * 解析消息
   */
  parseMessage(message) {
    try {
      if (message instanceof Buffer) {
        message = message.toString('utf8')
      }
      return JSON.parse(message)
    } catch (error) {
      console.error('消息解析失败:', error.message)
      return null
    }
  }

  /**
   * 处理心跳
   */
  handlePing(conn) {
    this.connectionManager.safeSend(conn, { cmd: WS_COMMANDS.PONG })
  }

  /**
   * 处理登录
   */
  async handleLogin(messageObj, conn, connId) {
    const { uid, roomId, msg } = messageObj
    
    if (!this.validateRequired(messageObj, ['uid', 'roomId'])) {
      return
    }

    try {
      const roomKey = REDIS_PREFIXES.ROOM + roomId
      
      // 检查用户是否已在房间
      const existingConnId = await redisManager.getCommandClient().hget(roomKey, uid)
      if (existingConnId) {
        await redisManager.removeUserFromRoom(roomId, uid)
      }

      // 处理额外信息
      const extraData = await this.handleExtraData(msg)
      
      // 获取房间成员和设备信息
      const { peerList, isCustomer } = await this.getRoomInfo(roomId, uid)
      
      // 发送当前成员列表
      this.connectionManager.safeSend(conn, {
        cmd: WS_COMMANDS.CURRENT_PEERS,
        msg: {
          peerList,
          ...extraData
        }
      })

      // 通知其他用户
      await this.notifyPeerJoin(roomId, uid, isCustomer, extraData)
      
      // 将用户加入房间
      await redisManager.addUserToRoom(roomId, uid, connId)
      await redisManager.setConnectionInfo(connId, roomId, uid)

      console.log(`${uid} 加入房间 ${roomId}`)
    } catch (error) {
      console.error('登录处理失败:', error.message)
    }
  }

  /**
   * 处理开始通话
   */
  async handleStartCall(messageObj, conn, connId) {
    const { uid, roomId } = messageObj
    
    if (!this.validateRequired(messageObj, ['uid', 'roomId'])) {
      return
    }

    try {
      // 检查客服是否在线
      const isOnline = await this.checkCustomerOnline(roomId)
      if (!isOnline) {
        this.connectionManager.safeSend(conn, {
          cmd: WS_COMMANDS.ERROR,
          msg: { type: ERROR_TYPES.OFFLINE }
        })
        return
      }

      // 检查是否已在通话中
      const isInCall = await redisManager.isInCall(roomId)
      if (isInCall) {
        this.connectionManager.safeSend(conn, {
          cmd: WS_COMMANDS.ERROR,
          msg: { type: ERROR_TYPES.IN_CALL }
        })
        return
      }

      // 开始通话
      await this.startCall(roomId, uid, connId, messageObj.msg)
      
      console.log(`${uid} 开始通话 ${roomId}`)
    } catch (error) {
      console.error('开始通话失败:', error.message)
    }
  }

  /**
   * 处理加入房间
   */
  async handleJoinRoom(messageObj, conn, connId) {
    const { uid, roomId } = messageObj
    
    if (!this.validateRequired(messageObj, ['uid', 'roomId'])) {
      return
    }

    try {
      const callKey = REDIS_PREFIXES.CALL + roomId
      
      // 通知房间内的其他人有新用户加入
      await this.broadcastToRoomExcept(callKey, uid, {
        cmd: WS_COMMANDS.NEW_PEER,
        uid,
        msg: messageObj.msg
      })
      
      console.log(`${uid} 加入房间 ${roomId}`)
    } catch (error) {
      console.error('加入房间失败:', error.message)
    }
  }

  /**
   * 处理中继消息
   */
  async handleRelay(messageObj) {
    const { remoteUid, roomId } = messageObj
    
    if (!this.validateRequired(messageObj, ['roomId', 'remoteUid'])) {
      console.log('中继消息缺少必要参数:', messageObj)
      return
    }

    try {
      const callKey = REDIS_PREFIXES.CALL + roomId
      const remoteConnId = await redisManager.getCommandClient().hget(callKey, remoteUid)
      
      if (remoteConnId) {
        console.log(`中继 ${messageObj.cmd} 从 ${messageObj.uid} 到 ${remoteUid}`)
        await redisManager.publishRelay(remoteConnId, messageObj)
      } else {
        console.log(`中继目标用户 ${remoteUid} 在房间 ${roomId} 中未找到`)
      }
    } catch (error) {
      console.error('中继处理失败:', error.message)
    }
  }

  /**
   * 处理房间广播
   */
  async handleRoomBroadcast(messageObj) {
    const { roomId, uid } = messageObj
    
    if (!this.validateRequired(messageObj, ['roomId', 'uid'])) {
      return
    }

    try {
      const callKey = REDIS_PREFIXES.CALL + roomId
      const callConn = await redisManager.getCommandClient().hget(callKey, uid)
      
      if (callConn) {
        await this.broadcastToRoomExcept(callKey, uid, messageObj)
        
        // 只有在不是 av_status 的情况下才清理 callKey
        if (messageObj.cmd !== WS_COMMANDS.AV_STATUS) {
          await redisManager.deleteCall(roomId)
        }
      }
    } catch (error) {
      console.error('房间广播处理失败:', error.message)
    }
  }

  /**
   * 验证必要参数
   */
  validateRequired(obj, requiredFields) {
    for (const field of requiredFields) {
      if (!obj[field]) {
        console.log(`缺少必要参数: ${field}`)
        return false
      }
    }
    return true
  }

  /**
   * 处理额外数据
   */
  async handleExtraData(msg) {
    const extraKey = 'cus:extra'
    if (!msg) {
      const dataStr = await redisManager.getCommandClient().get(extraKey)

      return JSON.parse(dataStr)
    }

    const extraData = {
      cus_channel: msg.cus_channel || '',
      cus_node: msg.cus_node || ''
    }

    // 存储到 Redis
    await redisManager.getCommandClient().set(extraKey, JSON.stringify(extraData))

    return extraData
  }

  /**
   * 获取房间信息
   */
  async getRoomInfo(roomId, uid) {
    const deviceKey = REDIS_PREFIXES.DEVICE + roomId
    const customerList = await redisManager.getRoomDevices(roomId)
    const roomMembers = await redisManager.getRoomMembers(roomId)
    const currentPeers = Object.keys(roomMembers)

    let isCustomer = false
    let connDevice = [] // 客服连接
    let clientDevice = [] // h5客户端用户

    for (const deviceId of customerList) {
      if (uid === deviceId) {
        isCustomer = true
      }
      const deviceConn = await redisManager.getCommandClient().hget(REDIS_PREFIXES.ROOM + roomId, deviceId)
      const connInfo = await redisManager.getConnectionInfo(deviceConn)
      if (connInfo) {
        connDevice.push(deviceId)
      }
    }

    for (const clientDeviceId of currentPeers) {
      if (!customerList.includes(clientDeviceId)) {
        clientDevice.push(clientDeviceId)
      }
    }

    let peerList = []
    if (isCustomer) {
      peerList = clientDevice
    } else if (connDevice.length > 0) {
      peerList = connDevice
    }

    return { peerList, isCustomer }
  }

  /**
   * 检查客服是否在线
   */
  async checkCustomerOnline(roomId) {
    const deviceKey = REDIS_PREFIXES.DEVICE + roomId
    const customerList = await redisManager.getRoomDevices(roomId)
    
    for (const deviceId of customerList) {
      const deviceConn = await redisManager.getCommandClient().hget(REDIS_PREFIXES.ROOM + roomId, deviceId)
      if (deviceConn) {
        return true
      }
    }
    return false
  }

  /**
   * 开始通话
   */
  async startCall(roomId, uid, connId, msg) {
    const deviceKey = REDIS_PREFIXES.DEVICE + roomId
    const customerList = await redisManager.getRoomDevices(roomId)

    // 维护通话状态
    await redisManager.addUserToCall(roomId, uid, connId)
    for (const deviceId of customerList) {
      const deviceConn = await redisManager.getCommandClient().hget(REDIS_PREFIXES.ROOM + roomId, deviceId)
      if (deviceConn) {
        await redisManager.addUserToCall(roomId, deviceId, deviceConn)
      }
    }

    // 通知房间内的其他人有新用户加入
    await this.broadcastToRoomExcept(REDIS_PREFIXES.CALL + roomId, uid, {
      cmd: WS_COMMANDS.START_CALL,
      roomId,
      uid,
      msg
    })
  }

  /**
   * 通知用户加入
   */
  async notifyPeerJoin(roomId, uid, isCustomer, extraData) {
    const deviceKey = REDIS_PREFIXES.DEVICE + roomId
    const customerList = await redisManager.getRoomDevices(roomId)
    const roomMembers = await redisManager.getRoomMembers(roomId)
    const currentPeers = Object.keys(roomMembers)

    if (isCustomer) {
      // 通知客户端用户
      for (const deviceId of currentPeers) {
        if (!customerList.includes(deviceId)) {
          const deviceConn = await redisManager.getCommandClient().hget(REDIS_PREFIXES.ROOM + roomId, deviceId)
          await redisManager.publishBroadcast(deviceConn, {
            cmd: WS_COMMANDS.PEER_JOIN,
            msg: { uid }
          })
        }
      }
    } else {
      // 通知客服
      for (const deviceId of customerList) {
        const deviceConn = await redisManager.getCommandClient().hget(REDIS_PREFIXES.ROOM + roomId, deviceId)
        if (deviceConn) {
          await redisManager.publishBroadcast(deviceConn, {
            cmd: WS_COMMANDS.PEER_JOIN,
            msg: {
              uid,
              ...extraData
            }
          })
        }
      }
    }
  }

  /**
   * 广播消息到房间内除指定用户外的所有用户
   */
  async broadcastToRoomExcept(key, exceptUid, payload) {
    try {
      const roomMembers = await redisManager.getCommandClient().hgetall(key)
      
      for (const [uid, connId] of Object.entries(roomMembers)) {
        if (uid === exceptUid) continue
        
        const connInfo = await redisManager.getConnectionInfo(connId)
        if (connInfo) {
          await redisManager.publishBroadcast(connId, payload)
        }
      }
    } catch (error) {
      console.error('房间广播失败:', error.message)
    }
  }
}

export default MessageHandler
